﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using FluentValidation;
using FluentValidation.Results;
using LINQPad.OpenAccess.Infrastructure;

namespace LINQPad.OpenAccess.UI.ViewModel
{
    /// <summary>
    /// Generic base class for validatable view models.
    /// </summary>
    /// <typeparam name="TViewModel">The type of the view model.</typeparam>
    public class ValidatableViewModelBase<TViewModel> : ValidatableViewModelBase, IDataErrorInfo
        where TViewModel : ValidatableViewModelBase
    {
        #region Private

        #region - Vars

        [DebuggerBrowsable( DebuggerBrowsableState.Never )]
        private IValidator<TViewModel> __validator;

        #endregion

        #endregion

        #region Protected

        #region - Functions

        /// <summary>
        /// Called to try to resolve the validator for the current instance.
        /// </summary>
        /// <returns></returns>
        protected virtual IValidator<TViewModel> TryResolveValidator( )
        {
            var serviceLocator = ServiceLocator.Current;
            var factory = serviceLocator.TryGet<IValidatorFactory>( );
            if ( factory == null )
                return null;
            return factory.GetValidator<TViewModel>( );
        }

        #endregion

        #region - Properties

        /// <summary>
        /// Gets the validator.
        /// </summary>
        /// <value>
        /// The validator.
        /// </value>
        protected virtual IValidator<TViewModel> Validator
        {
            get
            {
                return this.__validator ?? ( this.__validator = this.TryResolveValidator( ) );
            }
        }

        #endregion

        #endregion

        #region Public

        #region - Constructors

        /// <summary>
        /// Initializes a new instance of the <see cref="ValidatableViewModelBase{TViewModel}" /> class.
        /// </summary>
        /// <param name="validator">The validator.</param>
        /// <exception cref="System.InvalidOperationException">If the <typeparamref name="TViewModel" /> is not assignable by this instance.</exception>
        public ValidatableViewModelBase( IValidator<TViewModel> validator = null )
        {
            if ( !( this is TViewModel ) )
                throw new InvalidOperationException( String.Format( "This instance ('{0}') needs to be assignable from it's type parameter 'TViewModel' ('{1}') !", this.GetType( ).FullName, typeof( TViewModel ).FullName ) );

            this.__validator = validator;
        }

        #endregion

        #endregion

        #region ValidateableViewModelBase Members

        /// <summary>
        /// Validates the current instance.
        /// </summary>
        /// <param name="propertyName">Name of the property or <c>null</c>, if all properties should be validated.</param>
        /// <returns>
        /// The validation result or <c>null</c>.
        /// </returns>
        protected override ValidationResult ValidateInstance( string propertyName = null )
        {
            var validator = this.Validator;
            if ( validator == null )
                return null;
            if ( String.IsNullOrWhiteSpace( propertyName ) )
                return validator.Validate( this );
            return validator.Validate<TViewModel>( this as TViewModel, propertyName );
        }
        /// <summary>
        /// Gets the validation error message for the specified validation result.
        /// </summary>
        /// <param name="result">The result.</param>
        /// <param name="propertyName">Name of the property or <c>null</c>, if all properties were validated.</param>
        /// <returns>
        /// The error message or an empty string.
        /// </returns>
        protected override string GetValidationErrorMessage( ValidationResult result, string propertyName = null )
        {
            if ( result == null || result.IsValid )
                return String.Empty;

            IEnumerable<ValidationFailure> validationFailures = result.Errors;
            if ( !String.IsNullOrWhiteSpace( propertyName ) )
                validationFailures = validationFailures.Where( e => String.Equals( e.PropertyName, propertyName ) );

            return String.Join( Environment.NewLine, validationFailures.Select( e => e.ErrorMessage ) );
        }

        #endregion
    }
}
